Skip to content

Conversation

@s2imonovic
Copy link
Collaborator

@s2imonovic s2imonovic commented Oct 16, 2025

  • Adds fork tests for testing contract upgrades on live networks.
  • Includes BaseForkTest abstract contract and specific test implementations for GatewayEVM, ERC20Custody and GatewayZEVM contracts with state preservation verification.
  • Fork Testing: https://getfoundry.sh/forge/tests/fork-testing/

Closes: #571

Summary by CodeRabbit

  • Tests

    • Added standardized multi-chain upgrade testing framework for Ethereum, BSC, Polygon, Base, Arbitrum, and Avalanche
    • Introduced upgrade validation tests for ERC20Custody, GatewayEVM, and GatewayZEVM with automatic state preservation and permission verification
  • Chores

    • Removed deprecated upgrade simulation scripts

@s2imonovic s2imonovic requested review from a team as code owners October 16, 2025 18:21
@github-actions github-actions bot added the feat label Oct 16, 2025
@s2imonovic s2imonovic changed the title feat: add fork tests for upgradable contracts test: add fork tests for upgradable contracts Oct 16, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

📝 Walkthrough

Walkthrough

Introduces a unified fork testing framework (BaseForkTest) for standardized multi-chain proxy upgrade testing, replacing three legacy simulation scripts. Adds concrete test implementations for ERC20Custody, GatewayEVM, and GatewayZEVM with pre/post-upgrade state verification across multiple chains.

Changes

Cohort / File(s) Change Summary
New Fork Testing Framework
test/fork/BaseForkTest.sol
Added abstract base contract defining ChainConfig struct, public chains array, setUp() initialization, and internal virtual hooks (_setupChains, _testUpgradeContract) for downstream implementations. Includes test helpers for fork ID verification, fork switching, and cross-chain upgrade testing with implementation address retrieval utilities.
ERC20Custody Fork Test
test/fork/ERC20CustodyFork.t.sol
New test contract extending BaseForkTest to validate ERC20Custody proxy upgrades across 6 chains (Ethereum, BSC, Polygon, Base, Arbitrum, Avalanche). Implements _setupChains() to configure per-chain proxies and _testUpgradeContract() for pre/post-upgrade state validation (gateway, tssAddress, supportsLegacy) and admin role verification.
GatewayEVM Fork Test
test/fork/GatewayEVMFork.t.sol
New test contract extending BaseForkTest to validate GatewayEVM proxy upgrades across 6 chains. Implements _setupChains() for chain configuration and _testUpgradeContract() for upgrade flow with pre/post-upgrade state preservation checks (custody, tssAddress, zetaConnector, zetaToken).
GatewayZEVM Fork Test
test/fork/GatewayZEVMFork.t.sol
New test contract extending BaseForkTest for single-chain GatewayZEVM upgrade validation. Implements _setupChains() for ZetaChain configuration and _testUpgradeContract() with zetaToken state preservation and DEFAULT_ADMIN_ROLE verification.
Removed Upgrade Scripts
scripts/upgrade/SimulateERC20CustodyUpgrade.s.sol, scripts/upgrade/SimulateGatewayEVMUpgrade.s.sol, scripts/upgrade/SimulateGatewayZEVMUpgrade.s.sol
Deleted three legacy Forge simulation scripts that previously performed end-to-end upgrade simulations with state verification. Functionality consolidated into fork test contracts.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Fork Test
    participant Fork as Forge Fork
    participant Proxy as Proxy Contract
    participant Admin as Admin Account

    Test->>Fork: setUp() - create forks for all chains
    activate Fork
    Fork-->>Test: fork IDs returned
    deactivate Fork

    Test->>Test: testUpgradeGatewayOnAllChains()
    loop for each ChainConfig
        Test->>Fork: selectFork(forkId)
        Test->>Proxy: Read current implementation
        Test->>Proxy: Capture pre-upgrade state
        Test->>Test: Deploy new implementation
        Test->>Admin: vm.startPrank(adminAddress)
        Admin->>Proxy: upgradeToAndCall(newImpl, data)
        Test->>Proxy: Verify new implementation
        Test->>Proxy: Assert state preservation
        Test->>Admin: vm.stopPrank()
        Test->>Test: Log upgrade success
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

  • BaseForkTest: Abstract base class introduces fork management infrastructure; verify setUp() initialization logic, ChainConfig struct usage, and internal hook design for extensibility correctness
  • Test Implementations (ERC20CustodyFork.t.sol, GatewayEVMFork.t.sol, GatewayZEVMFork.t.sol): Three similar but distinct implementations with repetitive patterns; each requires validation of chain-specific addresses, upgrade flow logic, state capture/verification, and admin role checks—verify these per-contract correctness despite pattern consistency
  • Script Removals: Three deleted files with straightforward functionality; minimal review overhead

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "test: add fork tests for upgradable contracts" accurately summarizes the primary changes in this pull request. The changeset introduces a BaseForkTest abstract contract and concrete fork test implementations for three upgradable contracts (GatewayEVM, ERC20Custody, and GatewayZEVM) to validate upgrades on live networks, while also removing outdated upgrade simulation scripts. The title is concise, clear, and specific enough for team members scanning the repository history to understand that this PR adds fork-based testing infrastructure for contract upgrades.
Linked Issues Check ✅ Passed The pull request addresses the core objective of issue #571 by introducing fork tests that validate contract upgrades with comprehensive state preservation verification. The fork test implementations capture pre-upgrade state, deploy new implementations, perform upgrade operations via upgradeToAndCall, and verify that critical state variables (such as gateway, tssAddress, supportsLegacy, custody, zetaConnector, and zetaToken) are preserved post-upgrade. Additionally, the tests verify that administrative roles (DEFAULT_ADMIN_ROLE) are retained after upgrades. The PR also removes the old upgrade tests that only changed a single event without checking actual storage, directly addressing the comment to "delete the old upgrade tests." By testing against live network forks, these tests can detect storage layout incompatibilities and state-handling issues that would arise during real contract upgrades.
Out of Scope Changes Check ✅ Passed All changes in this pull request are directly aligned with the stated objectives of adding fork tests for testing contract upgrades and removing outdated upgrade simulation scripts. The BaseForkTest contract and its concrete implementations for GatewayEVM, ERC20Custody, and GatewayZEVM all serve the core objective. The deletion of the three upgrade simulation scripts (SimulateERC20CustodyUpgrade.s.sol, SimulateGatewayEVMUpgrade.s.sol, and SimulateGatewayZEVMUpgrade.s.sol) directly aligns with the comment to remove old upgrade tests. No changes appear to introduce functionality unrelated to the fork testing infrastructure for contract upgrades.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/fork-tests-upgrades

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added test and removed feat labels Oct 16, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6b9d7c0 and 42ddb51.

📒 Files selected for processing (4)
  • test/fork/BaseForkTest.sol (1 hunks)
  • test/fork/ERC20CustodyFork.t.sol (1 hunks)
  • test/fork/GatewayEVMFork.t.sol (1 hunks)
  • test/fork/GatewayZEVMFork.t.sol (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
test/**

⚙️ CodeRabbit configuration file

Review the test files for proper coverage, edge cases, and best practices.

Files:

  • test/fork/GatewayEVMFork.t.sol
  • test/fork/ERC20CustodyFork.t.sol
  • test/fork/GatewayZEVMFork.t.sol
  • test/fork/BaseForkTest.sol
🪛 GitHub Actions: Test
test/fork/GatewayEVMFork.t.sol

[error] 1-1: vm.envString: environment variable "ETHEREUM_RPC_URL" not found

test/fork/ERC20CustodyFork.t.sol

[error] 1-1: vm.envString: environment variable "ETHEREUM_RPC_URL" not found

test/fork/GatewayZEVMFork.t.sol

[error] 1-1: vm.envString: environment variable "ETHEREUM_RPC_URL" not found

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: slither
  • GitHub Check: generate

@s2imonovic
Copy link
Collaborator Author

s2imonovic commented Oct 16, 2025

@lumtis @skosito

  1. We can either hardcode the RPC URLs directly in the code or keep reading them from .env file (as we do now), but in that case, we should define them in the secrets.

  2. Can I delete the old upgrade tests that only change one event and don’t check the actual storage?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
test/fork/BaseForkTest.sol (1)

9-15: Guard missing RPC env vars to prevent CI failures.

This issue was already flagged in a previous review: vm.envString reverts when the environment variable is unset, causing CI failures when RPC URLs are missing. Use vm.envOr with a default value or check vm.envExists first, and update setUp() to gracefully skip or bail out when required RPC URLs are unavailable.

🧹 Nitpick comments (3)
test/fork/BaseForkTest.sol (3)

59-66: Prefer assertNotEq over assert for better test output.

Using assert provides minimal information on failure. Replace with assertNotEq(ethereumForkId, bscForkId, "Fork IDs must differ") (and so on) to get clearer diagnostics showing which fork IDs collided.

Apply this diff:

 function testForkIdDiffer() public view {
-    assert(ethereumForkId != bscForkId);
-    assert(bscForkId != polygonForkId);
-    assert(polygonForkId != baseForkId);
-    assert(baseForkId != arbitrumForkId);
-    assert(arbitrumForkId != avalancheForkId);
-    assert(avalancheForkId != zetachainForkId);
+    assertNotEq(ethereumForkId, bscForkId, "Ethereum and BSC fork IDs must differ");
+    assertNotEq(bscForkId, polygonForkId, "BSC and Polygon fork IDs must differ");
+    assertNotEq(polygonForkId, baseForkId, "Polygon and Base fork IDs must differ");
+    assertNotEq(baseForkId, arbitrumForkId, "Base and Arbitrum fork IDs must differ");
+    assertNotEq(arbitrumForkId, avalancheForkId, "Arbitrum and Avalanche fork IDs must differ");
+    assertNotEq(avalancheForkId, zetachainForkId, "Avalanche and ZetaChain fork IDs must differ");
 }

75-80: Consider renaming for clarity.

The function name testUpgradeGatewayOnAllChains is specific to Gateway contracts, but this is a base class that can test any upgradable contract (as evidenced by ERC20CustodyForkTest also using this base). Consider renaming to testUpgradeContractOnAllChains for better clarity.


45-55: Refactor fork creation to avoid requiring all RPC env vars
Eagerly calling vm.createFork for every supported chain in setUp() forces all seven RPC_URL env vars to be set, even if a subclass only needs a subset. Move those createFork calls into _setupChains() or guard against empty URLs so tests can opt into only the networks they actually use.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 42ddb51 and 9666280.

📒 Files selected for processing (1)
  • test/fork/BaseForkTest.sol (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
test/**

⚙️ CodeRabbit configuration file

Review the test files for proper coverage, edge cases, and best practices.

Files:

  • test/fork/BaseForkTest.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: generate
  • GitHub Check: slither
  • GitHub Check: test

Copy link
Member

@lumtis lumtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint256 avalancheForkId;
uint256 zetachainForkId;

// Mainnet admins.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only work for mainnet? We should precise it somewhere. Otherwise can't we make it dependent on the network used?

@lumtis
Copy link
Member

lumtis commented Oct 17, 2025

@lumtis @skosito

  1. We can either hardcode the RPC URLs directly in the code or keep reading them from .env file (as we do now), but in that case, we should define them in the secrets.
  2. Can I delete the old upgrade tests that only change one event and don’t check the actual storage?
  1. I think it's fine using public RPC, they are generally stable and we can still update the hardcoded value easily
  2. As I put in a comment, yes we can.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (1)

47-56: Action: Align withdraw() Natspec with actual role check (TSS vs WITHDRAWER_ROLE).

Natspec says "only TSS address" but the function uses onlyRole(WITHDRAWER_ROLE). Update the @dev to mention WITHDRAWER_ROLE or change the modifier to onlyRole(TSS_ROLE) depending on intended authority. File: test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (withdraw()).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9666280 and 9048ce0.

📒 Files selected for processing (32)
  • contracts/evm/ERC20Custody.sol (2 hunks)
  • contracts/evm/GatewayEVM.sol (6 hunks)
  • contracts/evm/Registry.sol (7 hunks)
  • contracts/evm/ZetaConnectorBase.sol (1 hunks)
  • contracts/evm/ZetaConnectorNative.sol (1 hunks)
  • contracts/evm/ZetaConnectorNonNative.sol (2 hunks)
  • contracts/evm/interfaces/IGatewayEVM.sol (3 hunks)
  • contracts/evm/legacy/ZetaConnector.base.sol (2 hunks)
  • contracts/evm/legacy/ZetaConnector.eth.sol (3 hunks)
  • contracts/evm/legacy/ZetaConnector.non-eth.sol (3 hunks)
  • contracts/evm/legacy/ZetaInterfaces.sol (1 hunks)
  • contracts/helpers/BaseRegistry.sol (3 hunks)
  • contracts/helpers/interfaces/IBaseRegistry.sol (3 hunks)
  • contracts/zevm/CoreRegistry.sol (7 hunks)
  • contracts/zevm/GatewayZEVM.sol (5 hunks)
  • contracts/zevm/ZRC20.sol (1 hunks)
  • contracts/zevm/interfaces/IGatewayZEVM.sol (3 hunks)
  • contracts/zevm/interfaces/UniversalContract.sol (2 hunks)
  • contracts/zevm/legacy/ZetaConnectorZEVM.sol (3 hunks)
  • test/CoreRegistry.t.sol (1 hunks)
  • test/ERC20Custody.t.sol (3 hunks)
  • test/GatewayEVM.t.sol (1 hunks)
  • test/GatewayEVMZEVM.t.sol (1 hunks)
  • test/GatewayZEVM.t.sol (2 hunks)
  • test/Registry.t.sol (1 hunks)
  • test/ZRC20.t.sol (1 hunks)
  • test/utils/ReceiverEVM.sol (1 hunks)
  • test/utils/upgrades/ERC20CustodyUpgradeTest.sol (2 hunks)
  • test/utils/upgrades/GatewayEVMUpgradeTest.sol (7 hunks)
  • test/utils/upgrades/GatewayZEVMUpgradeTest.sol (4 hunks)
  • test/utils/upgrades/ZetaConnectorNativeUpgradeTest.sol (1 hunks)
  • test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (2 hunks)
✅ Files skipped from review due to trivial changes (25)
  • contracts/evm/interfaces/IGatewayEVM.sol
  • contracts/zevm/legacy/ZetaConnectorZEVM.sol
  • contracts/evm/ZetaConnectorBase.sol
  • contracts/zevm/CoreRegistry.sol
  • test/GatewayEVMZEVM.t.sol
  • test/CoreRegistry.t.sol
  • contracts/evm/ERC20Custody.sol
  • contracts/zevm/ZRC20.sol
  • test/Registry.t.sol
  • test/ZRC20.t.sol
  • contracts/evm/legacy/ZetaConnector.non-eth.sol
  • contracts/evm/ZetaConnectorNonNative.sol
  • contracts/zevm/GatewayZEVM.sol
  • contracts/evm/ZetaConnectorNative.sol
  • contracts/evm/legacy/ZetaConnector.base.sol
  • test/ERC20Custody.t.sol
  • test/utils/upgrades/ERC20CustodyUpgradeTest.sol
  • test/GatewayZEVM.t.sol
  • test/GatewayEVM.t.sol
  • contracts/evm/legacy/ZetaConnector.eth.sol
  • contracts/helpers/interfaces/IBaseRegistry.sol
  • test/utils/upgrades/ZetaConnectorNativeUpgradeTest.sol
  • contracts/zevm/interfaces/IGatewayZEVM.sol
  • contracts/zevm/interfaces/UniversalContract.sol
  • test/utils/upgrades/GatewayEVMUpgradeTest.sol
🧰 Additional context used
📓 Path-based instructions (2)
contracts/**

⚙️ CodeRabbit configuration file

Review the Solidity contracts for security vulnerabilities and best practices.

Files:

  • contracts/helpers/BaseRegistry.sol
  • contracts/evm/Registry.sol
  • contracts/evm/legacy/ZetaInterfaces.sol
  • contracts/evm/GatewayEVM.sol
test/**

⚙️ CodeRabbit configuration file

Review the test files for proper coverage, edge cases, and best practices.

Files:

  • test/utils/ReceiverEVM.sol
  • test/utils/upgrades/GatewayZEVMUpgradeTest.sol
  • test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: generate
  • GitHub Check: slither
🔇 Additional comments (10)
test/utils/ReceiverEVM.sol (1)

76-83: LGTM! Formatting change only.

The function signature has been consolidated to a single line without any functional changes.

contracts/helpers/BaseRegistry.sol (1)

284-284: LGTM! Formatting changes align with PR-wide standardization.

These function declarations have been reformatted from multiline to single-line signatures without any semantic changes. The modifications are consistent with the broader formatting effort across the PR and introduce no security, correctness, or behavioral issues.

Also applies to: 298-298, 339-339

contracts/evm/Registry.sol (1)

35-35: LGTM! Formatting changes improve consistency.

The function declarations have been reformatted from multi-line to single-line forms without altering signatures, visibility, or behavior. This is consistent with the broader formatting standardization effort across the codebase.

Also applies to: 65-65, 96-96, 110-110, 124-124, 200-200, 222-222

contracts/evm/legacy/ZetaInterfaces.sol (1)

96-98: Formatting-only change; ABI/signature unchanged — OK.

Params, visibility, and return type are identical; selector unaffected. Good to merge.

contracts/evm/GatewayEVM.sol (1)

106-106: LGTM: Formatting consolidation improves consistency.

The function signature formatting changes (multi-line to single-line) are purely cosmetic and don't alter parameter types, names, visibility, or modifiers. The consolidation improves code consistency across the contract.

Also applies to: 127-127, 246-246, 263-263, 308-308, 426-426

test/utils/upgrades/GatewayZEVMUpgradeTest.sol (4)

149-171: Formatting-only change; behavior unchanged. Confirm event rename scope.

Signature reflow looks good. Also, only this ZRC20 withdraw emits WithdrawnV2 while other withdraw/withdrawAndCall still emit Withdrawn—confirm this asymmetry is intentional for the upgrade test.


255-277: LGTM: single-line signature reflow.

No ABI/behavior change introduced.


475-486: LGTM: signature reflow only.

No logic changes.


503-513: LGTM: signature reflow only.

No logic changes.

test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (1)

24-32: LGTM: initialize signature reflow.

No functional change.

…grades

# Conflicts:
#	contracts/evm/GatewayEVM.sol
#	test/utils/upgrades/GatewayEVMUpgradeTest.sol
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
contracts/evm/interfaces/IGatewayEVM.sol (1)

165-167: Consider standardizing function signature formatting across the interface.

The formatting consolidation creates an inconsistency within this interface file. Some function declarations now use single-line format (e.g., executeRevert, execute, depositAndCall), while others remain multi-line (e.g., executeWithERC20, revertWithERC20, other overloads of deposit/depositAndCall, and call).

For better readability and maintainability, consider applying consistent formatting throughout the interface—either single-line for all or multi-line for all.

Also applies to: 175-178, 224-226

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9048ce0 and 9b10fcf.

⛔ Files ignored due to path filters (6)
  • pkg/baseforktest.sol/baseforktest.go is excluded by !pkg/**
  • pkg/erc20custodyfork.t.sol/erc20custodyforktest.go is excluded by !pkg/**
  • pkg/gatewayevmfork.t.sol/gatewayevmforktest.go is excluded by !pkg/**
  • pkg/gatewayzevmfork.t.sol/gatewayzevmforktest.go is excluded by !pkg/**
  • types/BaseForkTest.ts is excluded by !types/**
  • types/factories/BaseForkTest__factory.ts is excluded by !types/**
📒 Files selected for processing (9)
  • contracts/evm/Registry.sol (7 hunks)
  • contracts/evm/ZetaConnectorNative.sol (1 hunks)
  • contracts/evm/interfaces/IGatewayEVM.sol (3 hunks)
  • contracts/helpers/BaseRegistry.sol (3 hunks)
  • contracts/helpers/interfaces/IBaseRegistry.sol (3 hunks)
  • contracts/zevm/CoreRegistry.sol (7 hunks)
  • contracts/zevm/GatewayZEVM.sol (5 hunks)
  • contracts/zevm/interfaces/UniversalContract.sol (2 hunks)
  • test/utils/upgrades/GatewayZEVMUpgradeTest.sol (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • contracts/zevm/GatewayZEVM.sol
  • contracts/evm/Registry.sol
  • test/utils/upgrades/GatewayZEVMUpgradeTest.sol
  • contracts/evm/ZetaConnectorNative.sol
  • contracts/helpers/BaseRegistry.sol
🧰 Additional context used
📓 Path-based instructions (1)
contracts/**

⚙️ CodeRabbit configuration file

Review the Solidity contracts for security vulnerabilities and best practices.

Files:

  • contracts/helpers/interfaces/IBaseRegistry.sol
  • contracts/zevm/interfaces/UniversalContract.sol
  • contracts/evm/interfaces/IGatewayEVM.sol
  • contracts/zevm/CoreRegistry.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: slither
  • GitHub Check: generate
🔇 Additional comments (3)
contracts/zevm/interfaces/UniversalContract.sol (1)

21-21: LGTM - Formatting consolidation.

The function declarations have been reformatted from multi-line to single-line format with no semantic changes.

Also applies to: 69-70

contracts/helpers/interfaces/IBaseRegistry.sol (1)

285-288: LGTM - Formatting consolidation.

The interface function declarations have been reformatted from multi-line to single-line format with no changes to signatures or behavior.

Also applies to: 295-298, 324-327

contracts/zevm/CoreRegistry.sol (1)

51-54: LGTM - Formatting consolidation.

All function declarations have been reformatted from multi-line to single-line format with no changes to parameters, modifiers, or logic.

Also applies to: 67-70, 83-86, 121-124, 160-160, 173-174, 197-198

@s2imonovic
Copy link
Collaborator Author

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
test/fork/ERC20CustodyFork.t.sol (1)

60-64: Admin preservation check added — resolves earlier feedback.

Good addition verifying DEFAULT_ADMIN_ROLE remains after upgrade.

🧹 Nitpick comments (5)
test/fork/ERC20CustodyFork.t.sol (5)

37-41: Capture a few more invariants to better cover storage layout.

If available on ERC20Custody, also snapshot: paused state, role memberships (e.g., UPGRADER_ROLE), any limits/caps, and a representative mapping value (if feasible). This increases sensitivity to layout shifts per Issue #571.


43-48: Assert upgrade authorization and Upgraded event; allow optional migration call.

  • Guard: expect revert if caller isn’t authorized (if configs change).
  • Assert EIP-1967 Upgraded(address) is emitted.
  • If the new impl requires a reinitializer, pass data via upgradeToAndCall.
-    vm.startPrank(config.admin);
+    vm.startPrank(config.admin);
+    vm.recordLogs();
     ERC20Custody localNewImplementation = new ERC20Custody();
-    custody.upgradeToAndCall(address(localNewImplementation), "");
+    // If a migration is needed in future, pass encoded call here instead of "".
+    custody.upgradeToAndCall(address(localNewImplementation), "");
+    Vm.Log[] memory logs = vm.getRecordedLogs();
+    bool sawUpgraded;
+    for (uint256 i; i < logs.length; ++i) {
+        // keccak256("Upgraded(address)")
+        if (logs[i].topics.length > 0 && logs[i].topics[0] == 0x5bfa8...000) { sawUpgraded = true; break; }
+    }
+    assertTrue(sawUpgraded, "Upgraded event not found");

Note: replace 0x5bfa8... with the exact topic keccak256("Upgraded(address)").


50-57: Verify new implementation code is non-empty and optionally ERC1822 proxiableUUID.

Extra safety so we don’t “upgrade” to a non-contract or wrong impl.

     address onChainNewImplementation = _getImplementation(address(custody));
     assertEq(onChainNewImplementation, address(localNewImplementation), "Upgrade failed.");
+    require(onChainNewImplementation.code.length > 0, "Impl has no code");
+    // Optional (UUPS): sanity-check ERC1822 UUID if available
+    // bytes32 uuid = custody.proxiableUUID();
+    // assertEq(uuid, 0x1822... , "Unexpected UUPS UUID");

55-57: Consider pre-validating that pre-upgrade values are sane (non-zero) to avoid false positives.

E.g., require(gatewayBefore != address(0)) so “unchanged” doesn’t pass when state was already empty due to wrong proxy.


1-2: CI/ops: decide on RPC sourcing and concurrency to stabilize fork tests.

  • Source RPCs from foundry.toml aliases with env overrides; prefer public endpoints for CI, private for local.
  • Limit forks per CI run or shard tests by chain to avoid rate limits/timeouts.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b10fcf and 0c33979.

⛔ Files ignored due to path filters (6)
  • data/addresses.testnet.json is excluded by !data/**
  • pkg/erc20custodyfork.t.sol/erc20custodyforktest.go is excluded by !pkg/**
  • pkg/gatewayevmfork.t.sol/gatewayevmforktest.go is excluded by !pkg/**
  • pkg/gatewayzevmfork.t.sol/gatewayzevmforktest.go is excluded by !pkg/**
  • types/factories/index.ts is excluded by !types/**
  • types/index.ts is excluded by !types/**
📒 Files selected for processing (6)
  • scripts/upgrade/SimulateERC20CustodyUpgrade.s.sol (0 hunks)
  • scripts/upgrade/SimulateGatewayEVMUpgrade.s.sol (0 hunks)
  • scripts/upgrade/SimulateGatewayZEVMUpgrade.s.sol (0 hunks)
  • test/fork/ERC20CustodyFork.t.sol (1 hunks)
  • test/fork/GatewayEVMFork.t.sol (1 hunks)
  • test/fork/GatewayZEVMFork.t.sol (1 hunks)
💤 Files with no reviewable changes (3)
  • scripts/upgrade/SimulateERC20CustodyUpgrade.s.sol
  • scripts/upgrade/SimulateGatewayZEVMUpgrade.s.sol
  • scripts/upgrade/SimulateGatewayEVMUpgrade.s.sol
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/fork/GatewayZEVMFork.t.sol
  • test/fork/GatewayEVMFork.t.sol
🧰 Additional context used
📓 Path-based instructions (1)
test/**

⚙️ CodeRabbit configuration file

Review the test files for proper coverage, edge cases, and best practices.

Files:

  • test/fork/ERC20CustodyFork.t.sol
🪛 GitHub Actions: Test
test/fork/ERC20CustodyFork.t.sol

[error] 1-1: Fork test failed: vm.envString: environment variable "ETHEREUM_RPC_URL" not found. Test ERC20CustodyForkTest constructors aborted. Command 'yarn test' exited with code 1.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: slither
  • GitHub Check: generate
🔇 Additional comments (1)
test/fork/ERC20CustodyFork.t.sol (1)

8-15: The hardcoded proxy address constants are not placeholders—they are the correct on-chain addresses from deployment records. Verify in data/addresses.mainnet.json and data/checksum/mainnet.json: Ethereum/BSC/Polygon/Base share 0x0Bad... and Arbitrum/Avalanche share 0xECe3... due to CREATE2 determinism with identical salts, confirmed by the broadcast record at broadcast/DeployERC20Custody.s.sol/1/run-latest.json where the proxy was deployed to 0x0Bad40D9e9C369f2223c835E108f43a45fd223B5 on Ethereum.

The test will execute correctly if RPC URLs and environment variables are properly configured. The defensive suggestions (adding code presence checks) remain reasonable best practices but are not required fixes.

Likely an incorrect or invalid review comment.

Comment on lines +16 to +29
function _setupChains() internal override {
chains.push(
ChainConfig(ethereumForkId, ETHEREUM_ERC20CUSTODY_PROXY, ETHEREUM_ADMIN, ETHEREUM_RPC_URL, "Ethereum")
);
chains.push(ChainConfig(bscForkId, BSC_ERC20CUSTODY_PROXY, BSC_ADMIN, BSC_RPC_URL, "BSC"));
chains.push(ChainConfig(polygonForkId, POLYGON_ERC20CUSTODY_PROXY, POLYGON_ADMIN, POLYGON_RPC_URL, "Polygon"));
chains.push(ChainConfig(baseForkId, BASE_ERC20CUSTODY_PROXY, BASE_ADMIN, BASE_RPC_URL, "Base"));
chains.push(
ChainConfig(arbitrumForkId, ARBITRUM_ERC20CUSTODY_PROXY, ARBITRUM_ADMIN, ARBITRUM_RPC_URL, "Arbitrum")
);
chains.push(
ChainConfig(avalancheForkId, AVALANCHE_ERC20CUSTODY_PROXY, AVALANCHE_ADMIN, AVALANCHE_RPC_URL, "Avalanche")
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CI failure: missing RPC envs. Add robust fallbacks and gating to avoid constructor aborts.

Current pipeline error: vm.envString "ETHEREUM_RPC_URL" not found. Provide defaults via foundry aliases/public RPCs and skip chains without URLs.

Action (in BaseForkTest.sol; outside this file):

  • Load RPCs with fallbacks to foundry.toml aliases and safe public endpoints; include gating so chains with empty URLs are not pushed.

Example (BaseForkTest.setUp):

// Foundry cheatcodes allow fallbacks
string memory ethUrl = vm.envOr("ETHEREUM_RPC_URL", vm.rpcUrlOr("ethereum", "https://ethereum.publicnode.com"));
string memory bscUrl = vm.envOr("BSC_RPC_URL",      vm.rpcUrlOr("bsc",      "https://bsc.publicnode.com"));
string memory polUrl = vm.envOr("POLYGON_RPC_URL",  vm.rpcUrlOr("polygon",  "https://polygon-bor.publicnode.com"));
string memory baseUrl= vm.envOr("BASE_RPC_URL",     vm.rpcUrlOr("base",     "https://base.publicnode.com"));
string memory arbUrl = vm.envOr("ARBITRUM_RPC_URL", vm.rpcUrlOr("arbitrum", "https://arbitrum-one.publicnode.com"));
string memory avaUrl = vm.envOr("AVALANCHE_RPC_URL",vm.rpcUrlOr("avalanche","https://avalanche-c-chain.publicnode.com"));

// Only create forks/push configs when URL is non-empty
if (bytes(ethUrl).length != 0) { ethereumForkId = vm.createFork(ethUrl); /* push chain config */ }

Optional gating in CI: require RUN_FORK_TESTS=true; otherwise skip:

bool runForks = vm.envOr("RUN_FORK_TESTS", false);
if (!runForks) return; // do not push any chains

Also add these to foundry.toml:

[rpc_endpoints]
ethereum = "${ETHEREUM_RPC_URL}"
bsc      = "${BSC_RPC_URL}"
polygon  = "${POLYGON_RPC_URL}"
base     = "${BASE_RPC_URL}"
arbitrum = "${ARBITRUM_RPC_URL}"
avalanche= "${AVALANCHE_RPC_URL}"
🤖 Prompt for AI Agents
In test/fork/ERC20CustodyFork.t.sol around lines 16-29 the test pushes chain
configs unconditionally which causes CI failures when RPC env vars are missing;
update BaseForkTest.setUp (not this file) to load RPC URLs with robust fallbacks
using vm.envOr and vm.rpcUrlOr (or safe public endpoints) for each chain, only
create forks and push ChainConfig entries when the resolved URL is non-empty,
and add an optional RUN_FORK_TESTS gating (vm.envOr) to skip all fork setup when
not enabled; also update foundry.toml rpc_endpoints to reference the env vars so
vm.rpcUrlOr can resolve aliases.

Comment on lines +31 to +36
function _testUpgradeContract(ChainConfig memory config) internal override {
vm.selectFork(config.forkId);

// Get the current proxy contract.
ERC20Custody custody = ERC20Custody(config.contractAddress);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Strengthen preconditions on the fork and proxy before upgrade.

Add sanity checks to fail fast with actionable messages.

     vm.selectFork(config.forkId);

     // Get the current proxy contract.
-    ERC20Custody custody = ERC20Custody(config.contractAddress);
+    require(config.forkId == vm.activeFork(), "Fork not active");
+    require(config.contractAddress.code.length > 0, "No code at proxy");
+    ERC20Custody custody = ERC20Custody(config.contractAddress);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _testUpgradeContract(ChainConfig memory config) internal override {
vm.selectFork(config.forkId);
// Get the current proxy contract.
ERC20Custody custody = ERC20Custody(config.contractAddress);
function _testUpgradeContract(ChainConfig memory config) internal override {
vm.selectFork(config.forkId);
// Get the current proxy contract.
require(config.forkId == vm.activeFork(), "Fork not active");
require(config.contractAddress.code.length > 0, "No code at proxy");
ERC20Custody custody = ERC20Custody(config.contractAddress);
🤖 Prompt for AI Agents
In test/fork/ERC20CustodyFork.t.sol around lines 31 to 36, add fast-fail sanity
checks: validate config.forkId is non-zero before selecting the fork, then after
vm.selectFork ensure config.contractAddress is not address(0) and that the
target address has on-chain code (address(config.contractAddress).code.length >
0) before casting to ERC20Custody; use require/assert with clear, actionable
error messages for each check so tests fail quickly with guidance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GatewayEVMUpgradeTest is not checking storage layout

3 participants